package cn.sciento.boot.data.change.parse;

import io.choerodon.mybatis.domain.EntityColumn;
import io.choerodon.mybatis.domain.EntityTable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.RowBounds;
import cn.sciento.boot.data.change.dto.ChangeData;
import cn.sciento.boot.data.change.dto.MybatisInvocation;
import cn.sciento.boot.data.change.dto.SqlParserInfo;
import cn.sciento.boot.data.change.utils.FieldUtil;
import cn.sciento.boot.data.change.utils.JsqlParserUtil;
import cn.sciento.boot.data.change.utils.MappedStatementUtil;
import cn.sciento.boot.data.change.utils.SqlExecuteUtil;
import cn.sciento.core.util.FieldNameUtils;

abstract class AbstractDataParser implements DataParser {
  public List<Map<String, Object>> queryBeforeAffectedData(MybatisInvocation mybatisInvocation) throws SQLException {
    List<Column> selectColumns = new ArrayList<>();
    selectColumns.add(new Column("*"));
    Expression whereExpression = mybatisInvocation.getSqlParserInfo().getWhereExpression();
    String selectSql = JsqlParserUtil.getSelect(mybatisInvocation.getSqlParserInfo().getTable(), selectColumns, whereExpression).toString();
    List<ParameterMapping> newParamMap = new ArrayList<>();
    List<ParameterMapping> parameterMappings = mybatisInvocation.getBoundSql().getParameterMappings();
    if (CollectionUtils.isNotEmpty(parameterMappings)) {
      newParamMap = parameterMappings.subList(parameterMappings.size() - JsqlParserUtil.getWhereColumnMappingCount(whereExpression).intValue(), parameterMappings.size());
    }
    BoundSql queryBoundSql = JsqlParserUtil.buildSelectBoundSql(mybatisInvocation, newParamMap, selectSql);
    MappedStatement selectMappedStatement = MappedStatementUtil.newHashMapMappedStatement(mybatisInvocation.getMappedStatement());
    Object queryResultList = mybatisInvocation.getExecutor().query(selectMappedStatement, mybatisInvocation.getParameter(), RowBounds.DEFAULT, null, null, queryBoundSql);
    ArrayList<Map<String, Object>> queryResults = (ArrayList<Map<String, Object>>)queryResultList;
    queryMultiLangData(mybatisInvocation, queryResults);
    return queryResults;
  }
  
  public List<Map<String, Object>> queryAfterAffectedData(MybatisInvocation mybatisInvocation, List<Map<String, Object>> affectedDatas) throws SQLException {
    List<Map<String, Object>> queryResults = new ArrayList<>();
    if (CollectionUtils.isEmpty(affectedDatas)) {
      return queryResults;
    }
    EntityTable table = mybatisInvocation.getTable();
    String keyColumn = table.getKeyColumns()[0];
    List<Object> keyValues = new ArrayList();
    affectedDatas.forEach(item -> keyValues.add(item.get(keyColumn)));
    String whereExpression = JsqlParserUtil.buildWhereExpression(keyValues, keyColumn);
    String tableName = table.getName();
    String selectSql = JsqlParserUtil.buildSelectSql(tableName, whereExpression);
    Connection connection = mybatisInvocation.getExecutor().getTransaction().getConnection();
    queryResults = SqlExecuteUtil.executeSql(connection, selectSql, null);
    queryMultiLangData(mybatisInvocation, queryResults);
    return queryResults;
  }
  
  public List<ChangeData> buildChangeData(List<Map<String, Object>> beforeAffectedDatas, List<Map<String, Object>> afterAffectedDatas, MybatisInvocation mybatisInvocation) {
    List<Map<String, Object>> temp;
    List<ChangeData> changeDatas = new ArrayList<>();
    List<String> changedAttributeList = new ArrayList<>();
    SqlParserInfo sqlParserInfo = mybatisInvocation.getSqlParserInfo();
    if (CollectionUtils.isNotEmpty(sqlParserInfo.getColumns())) {
      for (Column column : sqlParserInfo.getColumns()) {
        changedAttributeList.add(FieldNameUtils.underline2Camel(column.getColumnName(), true));
      }
    }
    String[] keyProperties = mybatisInvocation.getTable().getKeyProperties();
    String keyField = (mybatisInvocation.getTable().getKeyProperties() != null) ? keyProperties[0] : null;
    SqlCommandType sqlCommandType = mybatisInvocation.getMappedStatement().getSqlCommandType();
    String tableName = mybatisInvocation.getTable().getName();
    beforeAffectedDatas = FieldUtil.underlineToCamel(beforeAffectedDatas);
    afterAffectedDatas = FieldUtil.underlineToCamel(afterAffectedDatas);
    if (CollectionUtils.isNotEmpty(beforeAffectedDatas)) {
      temp = beforeAffectedDatas;
    } else {
      temp = afterAffectedDatas;
    } 
    boolean multiLanguage = mybatisInvocation.getTable().isMultiLanguage();
    for (Map<String, Object> object : temp) {
      ChangeData changeData = new ChangeData();
      Map<String, Object> before = beforeAffectedDatas.stream().filter(data -> Objects.equals(data.get(keyField), object.get(keyField))).findFirst().orElse(null);
      Map<String, Object> after = afterAffectedDatas.stream().filter(data -> Objects.equals(data.get(keyField), object.get(keyField))).findFirst().orElse(null);
      if (Objects.equals(before, after)) {
        continue;
      }
      changeData.setBeforeEntityMap(before);
      changeData.setAfterEntityMap(after);
      changeData.setAction(sqlCommandType);
      changeData.setChangedAttributeList(changedAttributeList);
      changeData.setKeyField(keyField);
      changeData.setKeyFieldValue(object.get(keyField));
      changeData.setTableName(tableName);
      changeData.setTlFlag(multiLanguage);
      changeData.setEntityClass(mybatisInvocation.getTable().getEntityClass());
      changeDatas.add(changeData);
    } 
    return changeDatas;
  }
  
  private void queryMultiLangData(MybatisInvocation mybatisInvocation, List<Map<String, Object>> queryResults) throws SQLException {
    EntityTable table = mybatisInvocation.getTable();
    if (!table.isMultiLanguage() || CollectionUtils.isEmpty(queryResults)) {
      return;
    }
    String keyColumn = table.getKeyColumns()[0];
    List<Object> keyValues = new ArrayList();
    queryResults.forEach(item -> keyValues.add(item.get(keyColumn)));
    String whereExpression = JsqlParserUtil.buildWhereExpression(keyValues, keyColumn);
    String multiLangTableName = table.getMultiLanguageTableName();
    String selectSql = JsqlParserUtil.buildSelectSql(multiLangTableName, whereExpression);
    Connection connection = mybatisInvocation.getExecutor().getTransaction().getConnection();
    List<Map<String, Object>> multiLangDataMapList = SqlExecuteUtil.executeSql(connection, selectSql, null);
    Set<EntityColumn> multiLangColumns = table.getMultiLanguageColumns();
    if (CollectionUtils.isEmpty(multiLangDataMapList)) {
      return;
    }
    Map<Object, List<Map<String, Object>>> groups = (Map<Object, List<Map<String, Object>>>)multiLangDataMapList.stream().collect(Collectors.groupingBy(item -> item.get(keyColumn)));
    for (Map<String, Object> resultMap : queryResults) {
      List<Map<String, Object>> multiLangDataMaps = groups.get(resultMap.get(keyColumn));
      HashMap<String, Object> multiLangDatas = new HashMap<>(2);
      for (EntityColumn entityColumn : multiLangColumns) {
        HashMap<String, String> multiLangAttribValue = new HashMap<>(2);
        for (Map<String, Object> multiLangDataMap : multiLangDataMaps) {
          multiLangAttribValue.put((String)multiLangDataMap.get("lang"), (String)multiLangDataMap.get(entityColumn.getColumn()));
        }
        multiLangDatas.put(entityColumn.getProperty(), multiLangAttribValue);
      } 
      resultMap.put("tls", multiLangDatas);
    } 
  }
}
